<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>quick start</title>
<meta name="keywords" content="test"/>
<meta name="description" content="test"/>
<meta name="robots" content="all"/>
<meta name="googlebot" content="all"/>
<meta name="baiduspider" content="all"/>
<meta name="copyright" content="All Rights Reserved 版权所有 test"/>
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7"/>
<link rel="shortcut icon" href="/favicon.ico"/>
<link rel="stylesheet" type="text/css" href="resource/css/doc.css"/>
<link type="text/css" rel="stylesheet" href="resource/sh/style/shCoreEclipse.css"/>
<link type="text/css" rel="stylesheet" href="resource/sh/style/shThemeEclipse.css"/>
<script type="text/javascript" src="resource/sh/shCore.js"></script>
<script type="text/javascript" src="resource/sh/shAutoloader.js"></script>
</head>
<body>
<div class="wrap">
    <div class="navbar">
        <em class="home"></em><a href="/index.html">home</a>
        <em></em><a href="/index.html">index</a>
    </div>
</div>
<div class="wrap">
    <h1 class="sk-h1">Ayada</h1>
    <div class="dir"></div>
    <h2 class="sk-h2">1 Ayada简介</h2>
    <div class="sk-content">
        <p>Ayada是一个Java版的采用jstl语法的模板引擎。所有的实现均与jsp中的jstl一样。对于习惯使用jstl的人来说几乎没有任何学习成本。</p>
        <ul class="sk-list">
            <li><p>语法完全同jsp语法，支持java脚本和jstl标签，对于使用jstl的人来说没有任何学习成本。</p></li>
            <li><p>零配置，不需要任何配置文件，也可以指定配置文件，配置文件简单明了。</p></li>
            <li><p>可插拔的模块设计，模板加载方式，el引擎，运行模式均可通过不同的实现替换。</p></li>
            <li><p>多种方式自定义标签，原生的java代码自定义标签或者直接通过页面自定义标签。</p></li>
            <li><p>支持自定义el转码，不需要对el表达式的输出做html转码，防止xss注入。</p></li>
            <li><p>简单快速的标签重写功能，专门针对用模板生成模板。</p></li>
            <li><p>支持java脚本和标签混合模式，这点与jsp一致，如果你追求极致性能那么可以完全使用java脚本编写模版。</p></li>
            <li><p>支持解释模式和编译模式，解释模式适合只需要在内存中运行的模板。编译模式性能更好。</p></li>
        </ul>
    </div>
    <h2 class="sk-h2">2 快速开始</h2>
    <h3 class="sk-h3">2.1 在控制台应用中使用</h3>
    <div class="sk-content">
        <p>只是一个最简单的使用方式，该方式采用解释模式执行。更多定制应用请参考下文相关章节。</p>
        <div class="sk-code">
            <pre class="brush: java;">
            public static void main(String[] args) {
                try {
                    StringWriter out = new StringWriter();
                    Ayada.execute("123&lt;c:if test=\"${1 == 1}\"&gt;abc&lt;/c:if&gt;xyz", null, out);
                    System.out.println(out.toString());
                }
                catch(Exception e) {
                    logger.warn(e.getMessage(), e);
                }
            }
            </pre>
        </div>
    </div>

    <h3 class="sk-h3">2.2 在web应用中使用</h3>
    <div class="sk-content">
        <p>Ayada是一个jsp/jstl引擎，所以当应用中注册filter接管jsp执行的时候，就会使用Ayada执行jsp，不注册该filter的时候，jsp仍然由容器执行。</p>
        <p>com.skin.ayada.web.filter.TemplateFilter只是一个默认实现，你也可以定制自己的filter，通过更加灵活的方式使用Ayada模板引擎。</p>
        <p>首先配置web.xml</p>
        <div class="sk-code">
            <pre class="brush: xml;">
                &lt;filter&gt;
                    &lt;filter-name&gt;TemplateFilter&lt;/filter-name&gt;
                    &lt;filter-class&gt;com.skin.ayada.web.filter.TemplateFilter&lt;/filter-class&gt;
                    &lt;init-param&gt;
                        &lt;param-name&gt;name&lt;/param-name&gt;
                        &lt;param-value&gt;templateFilter&lt;/param-value&gt;
                    &lt;/init-param&gt;
                    &lt;init-param&gt;
                        &lt;param-name&gt;home&lt;/param-name&gt;
                        &lt;param-value&gt;contextPath:/WEB-INF/template&lt;/param-value&gt;
                    &lt;/init-param&gt;
                    &lt;init-param&gt;
                        &lt;param-name&gt;prefix&lt;/param-name&gt;
                        &lt;param-value&gt;/WEB-INF/template&lt;/param-value&gt;
                    &lt;/init-param&gt;
                    &lt;init-param&gt;
                        &lt;param-name&gt;encoding&lt;/param-name&gt;
                        &lt;param-value&gt;utf-8&lt;/param-value&gt;
                    &lt;/init-param&gt;
                    &lt;init-param&gt;
                        &lt;param-name&gt;ignoreJspTag&lt;/param-name&gt;
                        &lt;param-value&gt;true&lt;/param-value&gt;
                    &lt;/init-param&gt;
                    &lt;init-param&gt;
                        &lt;param-name&gt;jspWork&lt;/param-name&gt;
                        &lt;param-value&gt;contextPath:/WEB-INF/ayada&lt;/param-value&gt;
                    &lt;/init-param&gt;
                    &lt;init-param&gt;
                        &lt;param-name&gt;sourceFactoryClass&lt;/param-name&gt;
                        &lt;param-value&gt;com.skin.ayada.source.DefaultSourceFactory&lt;/param-value&gt;
                    &lt;/init-param&gt;
                    &lt;init-param&gt;
                        &lt;param-name&gt;templateFactoryClass&lt;/param-name&gt;
                        &lt;param-value&gt;com.skin.ayada.runtime.JspTemplateFactory&lt;/param-value&gt;
                    &lt;/init-param&gt;
                    &lt;init-param&gt;
                        &lt;param-name&gt;expressionFactoryClass&lt;/param-name&gt;
                        &lt;param-value&gt;com.skin.ayada.runtime.DefaultExpressionFactory&lt;/param-value&gt;
                    &lt;/init-param&gt;
                &lt;/filter&gt;
                &lt;filter-mapping&gt;
                    &lt;filter-name&gt;TemplateFilter&lt;/filter-name&gt;
                    &lt;url-pattern&gt;/WEB-INF/template/*&lt;/url-pattern&gt;
                    &lt;dispatcher&gt;FORWARD&lt;/dispatcher&gt;
                    &lt;dispatcher&gt;ERROR&lt;/dispatcher&gt;
                &lt;/filter-mapping&gt;
            </pre>
        </div>
        <p class="bb">支持的参数列表</p>
        <p>name - 可选，TemplateDispatcher的名称，如果指定的话可以通过servletContext.getAttribute(name)获取到一个TemplateDispatcher对象。</p>
        <p>home - 可选，模板文件的根目录，其他的目录不允许访问。默认是应用的根目录。</p>
        <p>prefix - 可选，前缀，TemplateDispatcher会把指定的前缀去掉之后调用执行，因为前缀是指定给Filter用的。前缀应该与Filter的拦截前缀是一致的。</p>
        <p>encoding - 可选，模板文件的文件编码。</p>
        <p>contentType - 可选，如果指定会自动调用response.setContentType。只在response.getContentType为空的情况下调用。</p>
        <p>sourcePattern - 可选，模板文件的扩展名。</p>
        <p>jspWork - 可选，只在编译模式下有效，如果是编译模式，生成的class文件目录。</p>
        <p>zipFile - 可选，如果指定了ZipSourceFactory，则使用该参数加载zip或者jar文件。</p>
        <p>classPath - 可选，只在编译模式下有效，编译的时候使用的类路径，一般会自动识别，不需要手动设置。</p>
        <p>ignoreJspTag - 可选，只在编译模式下有效，是否支持java脚本，默认为true。</p>
        <p>sourceFactoryClass - 可选，使用的模板文件加载器。</p>
        <p>templateFactoryClass - 可选，模板加载器，对应的JspTemplateFactory会编译为class再执行。</p>
        <p>expressionFactoryClass - 可选，el表达式上下文工厂，通过它指定使用的el表达式引擎，默认是ognl。</p>

        <p class="bb">对应的servlet</p>
        <div class="sk-code">
            <pre class="brush: java;">
            public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
                // 从request中获取userId.
                long userId = 10001L;
                User user = UserService.getById(userId);

                // 将user对象放到request中。
                // TemplateFilter拦截到该请求的时候会自动从ServletContext、HttpSession、HttpServletRequest获取全部属性值
                // 放到Ayada的pageContext中，然后调用模板引擎执行。
                request.setAttribute("user", user);

                // TemplateFilter会拦截所有/template开头的请求作为模板处理。
                this.forward(request, response, "/WEB-INF/template/hello.jsp");
            }
            </pre>
        </div>
        <p class="bb">hello.jsp</p>
        <div class="sk-code">
            <pre class="brush: xml;">
                <h1>Hello, ${user.name} !</h1>
            </pre>
        </div>
    </div>
    <h3 class="sk-h3">2.4 与Spring集成</h3>
    <div class="sk-content">
        <p>请参考<a href="spring.html" target="_blank">与Spring集成</a>。</p>
    </div>

    <h3 class="sk-h3">2.4 自定义模板引擎</h3>
    <div class="sk-content">
        <div class="sk-code">
            <pre class="brush: java;">
            public void main(String[] args) {
                // 定义一个模板文件加载类, 对应的其他实现：ClassPathSourceFactory, ZipSourceFactory, MemorySourceFactory
                SourceFactory sourceFactory = new DefaultSourceFactory();

                // 定义一个模板文件加载类, 相关实现：
                // TemplateFactory - 创建一个模板对象，该对象的execute通过解释方式执行。
                // JspTemplateFactory - 将模版编译为class之后返回。
                TemplateFactory templateFactory = new TemplateFactory();

                // 表达式上下文工厂，返回的表达式上下文使用ognl执行。
                ExpressionFactory expressionFactory = new DefaultExpressionFactory();
                sourceFactory.setHome("D:\\myapp\template");
                sourceFactory.setSourcePattern("jsp,jspx");

                // 模版上下文对象
                TemplateContext templateContext = new DefaultTemplateContext();
                templateContext.setSourceFactory(sourceFactory);
                templateContext.setTemplateFactory(templateFactory);
                templateContext.setExpressionFactory(expressionFactory);

                Map&lt;String, Object&gt; context = new HashMap&lt;String, Object&gt;();
                context.put("user", "xuesong.net");
                StringWriter writer = new StringWriter();

                templateContext.execute("/hello.jsp", context, writer);
                System.out.println(writer.toString());
            }
            </pre>
        </div>
    </div>

    <h2 class="sk-h2">3 指令系统</h2>
    <div class="sk-content">
        <p>Ayada的语法包括三部分，分别是指令，标签和脚本，下面将分别叙述。</p>
    </div>
    <h3 class="sk-h3">3.1 指令系统</h2>
    <div class="sk-content">
        <p>Ayada的指令共有6个，都是编译指令，只在编译期生效。所有的编译指令都会删除掉指令前的空格和指令后的回车。关于如何处理标签和指令的空格和回车请参考下文相关章节。</p>
    </div>

    <h4 class="sk-h4">t:taglib 导入标签库</h4>
    <div class="sk-content">
        <div class="sk-code">
            <pre class="brush: xml;">
            &lt;t:taglib prefix="c" uri="http://localhost/tags/ayada-mytag"/&gt;
            </pre>
        </div>
        <p>编译器根据uri指定的路径到classpath下加载指定的标签库。</p>
        <p>例如上面的代码将会在classpath路径下加载/tags/ayada-mytag.xml文件。</p>
        <p class="bb">标签定义示例：</p>
        <div class="sk-code">
            <pre class="brush: xml;">
                &lt;?xml version="1.0" encoding="UTF-8"?&gt;
                &lt;taglib&gt;
                    &lt;tlib-version&gt;1.0&lt;/tlib-version&gt;
                    &lt;--
                        标签定义支持两种方式
                        1. 节点定义
                        2. 属性定义
                    --&gt;
                    &lt;tag&gt;
                        &lt;name&gt;c:set&lt;/name&gt;
                        &lt;tag-class&gt;com.skin.ayada.jstl.core.SetTag&lt;/tag-class&gt;
                        &lt;body-content&gt;jsp&lt;/body-content&gt;
                        &lt;ignore-whitespace&gt;true&lt;/ignore-whitespace&gt;
                        &lt;description&gt;&lt;/description&gt;
                    &lt;/tag&gt;
                    &lt;tag name="io:mkdir" tag-class="com.skin.ayada.jstl.io.MakeDirTag"
                        body-content="empty" ignore-whitespace="true" description=""/&gt;
                &lt;/taglib&gt;
            </pre>
            <p>name: 必选，标签名。</p>
            <p>tag-class: 必选，标签实现类。</p>
            <p>body-content: 可选，标签内容类型，默认是jsp。</p>
            <p>ignore-whitespace: 可选，是否忽略前导空格和后缀回车。前导空格包括空格和\t，不包括回车。后缀回车包括\r和\n，不包括空格和\t。默认为true。</p>
            <p>description: 可选，标签描述 。</p>
        </div>
    </div>
    <h4 class="sk-h4">t:import 导入一个标签</h4>
    <div class="sk-content">
        <div class="sk-code">
            <pre class="brush: xml;">
            &lt;t:import name="c:if" className="com.skin.ayada.jstl.core.IfTag"
                bodyContent="tagdependent" ignoreWhitespace="true" description="if标签"/&gt;
            </pre>
        </div>
        <p>className - 可选，如果为空则使用之前已经导入过的该标签的class，className为空适合重新定义bodyContent。</p>
        <p>bodyContent - 可选，如果为空则使用默认值jsp。</p>
        <p>ignoreWhitespace - 可选，默认为true。</p>
        <p>bodyContent取值只能是：tagdependent|jsp|empty</p>
        <p>jsp - 子节点可以是任何内容。这个也是默认值。</p>
        <p>tagdependent - 子节点只能是标签，任何文本内容都会在编译期被忽略掉。</p>
        <p>empty - 不允许存在任何子节点。节点内的任何内容都会在编译期被忽略掉。</p>
        <p>ignoreWhitespace - 是否忽略前导空格和后缀回车。前导空格包括空格和\t，不包括回车。后缀回车包括\r和\n，不包括空格和\t。</p>
    </div>

    <h4 class="sk-h4">t:rename 将已经导入的标签重命名为新的名字</h4>
    <div class="sk-content">
        <div class="sk-code">
            <pre class="brush: xml;">
            &lt;t:rename name="d:if" from="c:if"/&gt;
            </pre>
        </div>
        <p>只针对重命名之后的内容生效。该指令之前的内容仍将使用之前的标签名。</p>
    </div>

    <h4 class="sk-h4">t:include 包含一个文件</h4>
    <div class="sk-content">
        <div class="sk-code">
            <pre class="brush: xml;">
            &lt;t:include file="/include/script.html" type="script" encoding="UTF-8"/&gt;
            </pre>
        </div>
        <p>file - 必选 文件路径。</p>
        <p>type - 可选 取值: script|static，script: 被包含的文件作为模板文件编译，static: 被包含文件作为普通的文本内容，无论它是什么内容。此处指定的type优先级高于配置文件指定的source-pattern。如果未指定type，将根据扩展名自动处理。</p>
        <p>encoding - 可选 文件编码，如果不指定将使用TemplateContext指定的文件编码。</p>
        <p>t:include 指令是编译指令， 在编译期包含一个文件，先包含进来再一起编译。注意与系统提供的c:include，c:invoke，c:action区别。</p>
        <p>t:include 相当于jsp中的include指令，c:include相当于jsp中的jsp:include标签。t:include是指令，c:include是标签。</p>
        <p>Ayada同时支持jsp中的include指令和jsp:include标签。建议采用jsp语法编写，这样即使不采用Ayada，你的代码仍然可以通过容器执行。</p>
        <p>但是Ayada的t:include指令和c:include都做了扩展，功能增强，支持静态包含和传递参数，如果你希望使用Ayada的扩展功能，就必须使用Ayada的标签。</p>
    </div>

    <h4 class="sk-h4">t:text 文本指令</h4>
    <div class="sk-content">
        <div class="sk-code">
            <pre class="brush: xml;">
            &lt;t:text escape="xml"&gt;&lt;c:if test="${1==1}"&gt;hello&lt;/c:if&gt;&lt;/t:text&gt;
            </pre>
        </div>
        <p>escape - 可选，是否做xml编码。是在编译期编码。</p>
        <p>t:text 其中的任何内容都作为文本处理，不允许嵌套。</p>
    </div>

    <h4 class="sk-h4">t:comment 注释指令</h4>
    <div class="sk-content">
        <div class="sk-code">
            <pre class="brush: xml;">
            &lt;t:comment&gt;&lt;c:if test="${1==1}"&gt;hello&lt;/c:if&gt;&lt;/t:comment&gt;
            </pre>
        </div>
        <p>t:comment 其中的任何内容都作为注释被忽略，不允许嵌套。</p>
    </div>

    <h3 class="sk-h3">3.2 内置的标签列表</h3>
    <div class="sk-content">
        <p>内置标签是Ayada内置的常用标签，你也可以重新定义一套自己的标签库并使用。关于自定义标签请参考<a href="#cp_5">5 自定义标签</a>章节。</p>
        <p>凡是实现了BodyContentSupport接口的都意味者你可以在子节点里面写任意内容。</p>
        <div class="sk-code">
            <table class="sk-grid">
                <tr>
                    <td style="width: 200px">标签</td>
                    <td style="width: 200px">实现</td>
                    <td style="width: 200px">类型</td>
                    <td style="width: 200px">说明&示例</td>
                </tr>
                <tr>
                    <td>c:dependent</td>
                    <td>com.skin.ayada.jstl.core.DependentTag</td>
                    <td>tagdependent</td>
                    <td><p>子节点只能是标签，可以是任何标签，但不允许任何文本和表达式。只限制子节点，不限制子节点的子节点。</p></td>
                </tr>
                <tr>
                    <td>c:command</td>
                    <td>com.skin.ayada.jstl.core.DependentTag</td>
                    <td>tagdependent</td>
                    <td><p>子节点只能是标签，可以是任何标签，但不允许任何文本和表达式。只限制子节点，不限制子节点的子节点。</p></td>
                </tr>
                <tr>
                    <td>c:if</td>
                    <td>com.skin.ayada.jstl.core.IfTag</td>
                    <td>jsp</td>
                    <td>&nbsp;</td>
                </tr>
                <tr>
                    <td>c:set</td>
                    <td>com.skin.ayada.jstl.core.SetTag</td>
                    <td>jsp</td>
                    <td>&nbsp;</td>
                </tr>
                <tr>
                    <td>c:remove</td>
                    <td>com.skin.ayada.jstl.core.RemoveTag</td>
                    <td>empty</td>
                    <td><p>从pageContext中移除一个指定的属性。</p></td>
                </tr>
                <tr>
                    <td>c:out</td>
                    <td>com.skin.ayada.jstl.core.OutTag</td>
                    <td>jsp</td>
                    <td>
                        <p>实现了BodyTagSupport接口</p>
                        <p>&lt;c:param name="myVar" value="${user.name}"&gt;</p>
                        <p>&lt;c:param name="myVar"&gt;&lt;h1&gt;Hello World&lt;/h1&gt;&lt;/c:param&gt;</p>
                    </td>
                </tr>
                <tr>
                    <td>c:ifout</td>
                    <td>com.skin.ayada.jstl.core.IfOutTag</td>
                    <td>empty</td>
                    <td>&nbsp;</td>
                </tr>
                <tr>
                    <td>c:nvl</td>
                    <td>com.skin.ayada.jstl.core.NVLTag</td>
                    <td>empty</td>
                    <td>&nbsp;</td>
                </tr>
                <tr>
                    <td>c:catch</td>
                    <td>com.skin.ayada.jstl.core.CatchTag</td>
                    <td>jsp</td>
                    <td>&nbsp;</td>
                </tr>
                <tr>
                    <td>c:each</td>
                    <td>com.skin.ayada.jstl.core.ForEachTag</td>
                    <td>jsp</td>
                    <td>&nbsp;</td>
                </tr>
                <tr>
                    <td>c:forEach</td>
                    <td>com.skin.ayada.jstl.core.ForEachTag</td>
                    <td>jsp</td>
                    <td>&nbsp;</td>
                </tr>
                <tr>
                    <td>c:forTokens</td>
                    <td>com.skin.ayada.jstl.core.ForTokensTag</td>
                    <td>jsp</td>
                    <td>&nbsp;</td>
                </tr>
                <tr>
                    <td>c:choose</td>
                    <td>com.skin.ayada.jstl.core.ChooseTag</td>
                    <td>tagdependent</td>
                    <td>&nbsp;</td>
                </tr>
                <tr>
                    <td>c:when</td>
                    <td>com.skin.ayada.jstl.core.WhenTag</td>
                    <td>jsp</td>
                    <td>&nbsp;</td>
                </tr>
                <tr>
                    <td>c:otherwise</td>
                    <td>com.skin.ayada.jstl.core.OtherwiseTag</td>
                    <td>jsp</td>
                    <td>&nbsp;</td>
                </tr>
                <tr>
                    <td>c:continue</td>
                    <td>com.skin.ayada.jstl.core.ContinueTag</td>
                    <td>empty</td>
                    <td><p>暂不支持的功能。</p></td>
                </tr>
                <tr>
                    <td>c:break</td>
                    <td>com.skin.ayada.jstl.core.BreakTag</td>
                    <td>empty</td>
                    <td><p>暂不支持的功能。</p></td>
                </tr>
                <tr>
                    <td>c:join</td>
                    <td>com.skin.ayada.jstl.core.JoinTag</td>
                    <td>empty</td>
                    <td>
                        <p>items - 数组，或者逗号分隔字符串，或者list，或者iterator，或者map。遵循forEach标签的items定义。</p>
                        <p>如果指定了items就不能再指定value和repeat，二者互斥。</p>
                        <p>value - 任何数据类型，要重复的内容。</p>
                        <p>repeat - 重复次数。</p>
                        <p>separator - 分隔符。</p>
                        <p>nullable - 是否显示null值，只针对指定了items时有效。</p>
                        <p>例如：&lt;c:join value="?" repeat="5" separator=", "/&gt;</p>
                        <p>输出：?, ?, ?, ?, ?</p>
                    </td>
                </tr>
                <tr>
                    <td>c:comment</td>
                    <td>com.skin.ayada.jstl.core.CommentTag</td>
                    <td>empty</td>
                    <td>
                        <p>其中的任何内容都不会执行，直接跳过。</p>
                        <p>与t:comment相同，但是c:comment会存在，但不执行。t:comment运行期根本就不存在。</p>
                    </td>
                </tr>
                <tr>
                    <td>c:escape</td>
                    <td>com.skin.ayada.jstl.core.EscapeTag</td>
                    <td>empty</td>
                    <td>
                        <p>设置el表达式和jsp表达式的编码器。</p>
                        <p>&lt;c:escape encoder="${myEncoder}"/&gt;</p>
                        <p>encoder可以是一个实现了ELEncoder接口的对象，也可以是字符串: xml|html。</p>
                    </td>
                </tr>
                <tr>
                    <td>c:print</td>
                    <td>com.skin.ayada.jstl.core.PrintTag</td>
                    <td>empty</td>
                    <td><p>打印对象详情。</p></td>
                </tr>
                <tr>
                    <td>c:exit</td>
                    <td>com.skin.ayada.jstl.core.ExitTag</td>
                    <td>empty</td>
                    <td><p>无条件终止模板渲染，立即退出。遵循try catch规则，退出之前会执行tryCatch标签，如果有的话。</p></td>
                </tr>
                <tr>
                    <td>c:buffer</td>
                    <td>com.skin.ayada.jstl.core.BufferTag</td>
                    <td>jsp</td>
                    <td>
                        <p>实现了BodyTagSupport接口</p>
                        <p>var - 必选，变量名，如果未指定，内容将不会执行，因为没有必要执行。</p>
                        <p>将标签内的内容执行之后放到pageContext中，name是该内容的引用名。</p>
                    </td>
                </tr>
                <tr>
                    <td>c:bean</td>
                    <td>com.skin.ayada.jstl.core.BeanTag</td>
                    <td>tagdependent</td>
                    <td><p>构建一个java bean对象。</p></td>
                </tr>
                <tr>
                    <td>c:param</td>
                    <td>com.skin.ayada.jstl.core.ParameterTag</td>
                    <td>jsp</td>
                    <td>
                        <p>实现了BodyTagSupport接口</p>
                        <p>&lt;c:param name="myVar" value="${user.name}"&gt;</p>
                        <p>&lt;c:param name="myVar"&gt;&lt;h1&gt;Hello World&lt;/h1&gt;&lt;/c:param&gt;</p>
                    </td>
                </tr>
                <tr>
                    <td>c:property</td>
                    <td>com.skin.ayada.jstl.core.PropertyTag</td>
                    <td>jsp</td>
                    <td>
                        <p>实现了BodyTagSupport接口</p>
                        <p>&lt;c:property name="myVar" value="${user.name}"&gt;</p>
                        <p>&lt;c:property name="myVar"&gt;&lt;h1&gt;Hello World&lt;/h1&gt;&lt;/c:property&gt;</p>
                    </td>
                </tr>
                <tr>
                    <td>c:attribute</td>
                    <td>com.skin.ayada.jstl.core.AttributeTag</td>
                    <td>jsp</td>
                    <td>
                        <p>实现了BodyTagSupport接口</p>
                        <p>&lt;c:attribute name="myVar" value="${user.name}"&gt;</p>
                        <p>&lt;c:attribute name="myVar"&gt;&lt;h1&gt;Hello World&lt;/h1&gt;&lt;/c:attribute&gt;</p>
                    </td>
                </tr>
                <tr>
                    <td>c:constructor</td>
                    <td>com.skin.ayada.jstl.core.ConstructorTag</td>
                    <td>empty</td>
                    <td>&lt;c:constructor type="int" value/&gt;配合c:bean使用，用来指定构造函数的参数，构造函数支持重载，自动适配。</td>
                </tr>
                <tr>
                    <td>c:map</td>
                    <td>com.skin.ayada.jstl.core.MapTag</td>
                    <td>tagdependent</td>
                    <td>
                        <p>构建一个map对象，元素通过c:entry指定。</p>
                        <p>其实我们应该通过引入json库，通过json构建，应用方可自行实现。</p>
                    </td>
                </tr>
                <tr>
                    <td>c:list</td>
                    <td>com.skin.ayada.jstl.core.ListTag</td>
                    <td>tagdependent</td>
                    <td>
                        <p>构建一个list对象，元素通过c:element指定。</p>
                        <p>其实我们应该通过引入json库，通过json构建，应用方可自行实现。</p>
                    </td>
                </tr>
                <tr>
                    <td>c:entry</td>
                    <td>com.skin.ayada.jstl.core.AttributeTag</td>
                    <td>jsp</td>
                    <td>
                        <p>实现了BodyTagSupport接口</p>
                        <p>配合c:map使用，指定map的元素。</p>
                        <p>&lt;c:element index="0" value="${user.name}"&gt;</p>
                        <p>&lt;c:element index="1"&gt;&lt;h1&gt;Hello World&lt;/h1&gt;&lt;/c:element&gt;</p>
                    </td>
                </tr>
                <tr>
                    <td>c:element</td>
                    <td>com.skin.ayada.jstl.core.ElementTag</td>
                    <td>jsp</td>
                    <td>
                        <p>实现了BodyTagSupport接口</p>
                        <p>配合c:list使用，指定list的元素。</p>
                        <p>&lt;c:element index="0" value="${user.name}"&gt;</p>
                        <p>&lt;c:element index="1"&gt;&lt;h1&gt;Hello World&lt;/h1&gt;&lt;/c:element&gt;</p>
                    </td>
                </tr>
                <tr>
                    <td>c:execute</td>
                    <td>com.skin.ayada.jstl.core.ExecuteTag</td>
                    <td>empty</td>
                    <td><p>同c:set，执行一个el表达式，并将结果存入pageContext。</p></td>
                </tr>
                <tr>
                    <td>c:xml</td>
                    <td>com.skin.ayada.jstl.core.OutputTag</td>
                    <td>tagdependent</td>
                    <td><p>输出内容到指定文件，请参考下文。</p></td>
                </tr>
                <tr>
                    <td>c:output</td>
                    <td>com.skin.ayada.jstl.core.OutputTag</td>
                    <td>jsp</td>
                    <td><p>输出内容到指定文件，请参考下文。</p></td>
                </tr>
                <tr>
                    <td>c:cache</td>
                    <td>com.skin.ayada.jstl.core.CacheTag</td>
                    <td>jsp</td>
                    <td>
                        <p>缓存标签。</p>
                        <p>cache - 可选，缓存实现类，不需要实现任何接口，只要存在setCache和getCache方法即可。</p>
                        <p>如果为空，则从pageContext里自动获取名称为cacheClient的对象。如果也获取不到，则执行内容。</p>
                        <p>key - 缓存数据的key。</p>
                        <p>expires - 缓存时间。</p>
                    </td>
                </tr>
                <tr>
                    <td>c:include</td>
                    <td>com.skin.ayada.taglib.ActionTag</td>
                    <td>tagdependent</td>
                    <td><p>本质是在另外一个新的pageContext中调用另外一个模板，支持参数。请参考下文。</p></td>
                </tr>
                <tr>
                    <td>c:invoke</td>
                    <td>com.skin.ayada.taglib.ActionTag</td>
                    <td>tagdependent</td>
                    <td><p>本质是在另外一个新的pageContext中调用另外一个模板，支持参数。请参考下文。</p></td>
                </tr>
                <tr>
                    <td>c:layout</td>
                    <td>com.skin.ayada.taglib.ActionTag</td>
                    <td>tagdependent</td>
                    <td><p>本质是在另外一个新的pageContext中调用另外一个模板，支持参数。请参考下文。</p></td>
                </tr>
                <tr>
                    <td>c:action</td>
                    <td>com.skin.ayada.taglib.ActionTag</td>
                    <td>tagdependent</td>
                    <td><p>本质是在另外一个新的pageContext中调用另外一个模板，支持参数。请参考下文。</p></td>
                </tr>
                <tr>
                    <td>c:test</td>
                    <td>com.skin.ayada.jstl.core.TestTag</td>
                    <td>empty</td>
                    <td><p>用来测试，应用方请勿使用。</p></td>
                </tr>
                <tr>
                    <td>c:tryCatchTest</td>
                    <td>com.skin.ayada.jstl.core.TryCatchTestTag</td>
                    <td>jsp</td>
                    <td><p>用来测试，应用方请勿使用。</p></td>
                </tr>
                <tr>
                    <td>c:scrollpage</td>
                    <td>com.skin.ayada.taglib.ScrollPage</td>
                    <td>empty</td>
                    <td><p>一个翻页的实现。</p></td>
                </tr>
                <tr>
                    <td>f:substring</td>
                    <td>com.skin.ayada.jstl.fmt.SubstringTag</td>
                    <td>empty</td>
                    <td><p>字符串截取。</p></td>
                </tr>
                <tr>
                    <td>fmt:formatDate</td>
                    <td>com.skin.ayada.jstl.fmt.DateFormatTag</td>
                    <td>empty</td>
                    <td><p>日期格式化，参数支持long, Date类型</p></td>
                </tr>
                <tr>
                    <td>fmt:formatNumber</td>
                    <td>com.skin.ayada.jstl.fmt.NumberFormatTag</td>
                    <td>empty</td>
                    <td><p>数字格式化。</p></td>
                </tr>
                <tr>
                    <td>fmt:parseDate</td>
                    <td>com.skin.ayada.jstl.fmt.ParseDateTag</td>
                    <td>jsp</td>
                    <td><p>解析日期。</p></td>
                </tr>
                <tr>
                    <td>fmt:parseNumber</td>
                    <td>com.skin.ayada.jstl.fmt.ParseNumberTag</td>
                    <td>jsp</td>
                    <td><p>解析数字。</p></td>
                </tr>
                <tr>
                    <td>fmt:setTimeZone</td>
                    <td>com.skin.ayada.jstl.fmt.SetTimeZoneTag</td>
                    <td>empty</td>
                    <td><p>设置时区。</p></td>
                </tr>
                <tr>
                    <td>fmt:timeZone</td>
                    <td>com.skin.ayada.jstl.fmt.TimeZoneTag</td>
                    <td>jsp</td>
                    <td><p>设置时区。</p></td>
                </tr>
                <tr>
                    <td>fmt:setLocale</td>
                    <td>com.skin.ayada.jstl.fmt.SetLocaleTag</td>
                    <td>empty</td>
                    <td><p>设置Locale。</p></td
                </tr>
                <tr>
                    <td>fmt:bundle</td>
                    <td>com.skin.ayada.jstl.fmt.BundleTag</td>
                    <td>jsp</td>
                    <td><p>国际化相关</p></td>
                </tr>
                <tr>
                    <td>fmt:setBundle</td>
                    <td>com.skin.ayada.jstl.fmt.SetBundleTag</td>
                    <td>empty</td>
                    <td><p>国际化相关</p></td>
                </tr>
                <tr>
                    <td>fmt:message</td>
                    <td>com.skin.ayada.jstl.fmt.MessageTag</td>
                    <td>tagdependent</td>
                    <td><p>国际化相关</p></td>
                </tr>
                <tr>
                    <td>fmt:param</td>
                    <td>com.skin.ayada.jstl.fmt.FmtParamTag</td>
                    <td>jsp</td>
                    <td><p>国际化相关</p></td>
                </tr>
                <tr>
                    <td>sql:connect</td>
                    <td>com.skin.ayada.jstl.sql.ConnectTag</td>
                    <td>jsp</td>
                    <td>
                        <p>建立一个数据库链接，实现了tryCacheTag，只有该标签创建的连接才会在finally里面关闭，<br/>如果是外部传入的连接则不关闭，遵循谁创建谁关闭原则。</p>
                        <p>该标签有多种使用方式，请参考webapp/command.jsp</p>
                    </td>
                </tr>
                <tr>
                    <td>sql:execute</td>
                    <td>com.skin.ayada.jstl.sql.SqlTag</td>
                    <td>jsp</td>
                    <td>
                        <p>执行指定的sql。</p>
                        <p>该标签有多种使用方式，请参考webapp/command.jsp</p>
                    </td>
                </tr>
                <tr>
                    <td>sql:query</td>
                    <td>com.skin.ayada.jstl.sql.QueryTag</td>
                    <td>jsp</td>
                    <td>
                        <p>执行sql查询。请参考webapp/command.jsp。</p>
                    </td>
                </tr>
                <tr>
                    <td>sql:parse</td>
                    <td>com.skin.ayada.jstl.sql.CreateParseTag</td>
                    <td>jsp</td>
                    <td>
                        <p>解析create语句。请参考webapp/command.jsp。</p>
                    </td>
                </tr>
                <tr>
                    <td>sql:create</td>
                    <td>com.skin.ayada.jstl.sql.CreateParseTag</td>
                    <td>jsp</td>
                    <td>
                        <p>解析create语句。请参考webapp/command.jsp。</p>
                    </td>
                </tr>
                <tr>
                    <td>sql:insert</td>
                    <td>com.skin.ayada.jstl.sql.InsertParseTag</td>
                    <td>jsp</td>
                    <td>
                        <p>解析insert语句。请参考webapp/command.jsp。</p>
                    </td>
                </tr>
                <tr>
                    <td>io:copy</td>
                    <td>com.skin.ayada.jstl.io.CopyTag</td>
                    <td>tagdependent</td>
                    <td>
                        <p>拷贝文件或者目录到指定位置。</p>
                    </td>
                </tr>
                <tr>
                    <td>io:delete</td>
                    <td>com.skin.ayada.jstl.io.DeleteTag</td>
                    <td>tagdependent</td>
                    <td>
                        <p>删除文件或者目录。</p>
                    </td>
                </tr>
                <tr>
                    <td>io:mkdir</td>
                    <td>com.skin.ayada.jstl.io.MakeDirTag</td>
                    <td>empty</td>
                    <td>
                        <p>创建一个目录。</p>
                    </td>
                </tr>
            </table>
        </div>
    </div>

    <h3 class="sk-h2">3.3 常用标签介绍</h3>
    <div class="sk-content">
        <p>一般标签（例如: c:if, c:set, c:out, c:forEach等）就不介绍了，与jstl中的使用方式一样。此处只介绍一些在开发中很常用的开发模式，但是jstl又没有提供或者不支持的功能。</p>
    </div>
    <h4 class="sk-h4">c:escape</h4>
    <div class="sk-content">
        <div class="sk-code"><pre class="brush: xml;">&lt;c:escape encoder="${myHtmlEncoder}"/&gt;</div>
        <p>参数encoder接受两种参数：</p>
        <p>对象: 必须实现com.skin.ayada.runtime.ELEncoder。</p>
        <p>字符串: xml|html|null。</p>
        <p>一般情况下不需要设置，默认对el表达式做html编码之后输出。如果不需要对输出做编码，可以使用c:out标签输出或者在el表达式前面加#。</p>
        <p>如果设置为空，则之后的所有el和jsp表达式都不再做编码，直到重新设置。</p>
    </div>

    <h4 class="sk-h4">c:print</h4>
    <div class="sk-content">
        <div class="sk-code"><pre class="brush: xml;">&lt;c:print out="${pageContext}" value="${user}"/&gt;</div>
        <p>out：java.io.Writer - 可选 默认是页面的writer对象，也可以是pageContext对象。</p>
        <p>value: java.lang.Object</p>
        <p>打印对象，通常用来调试。</p>
    </div>

    <h4 class="sk-h4">c:exit</h4>
    <div class="sk-content">
        <div class="sk-code"><pre class="brush: xml;">&lt;c:exit test="${1 == 1}"/&gt;</div>
        <p>立即终止当前页面渲染。</p>
        <p>test - 可选，默认为true。</p>
    </div>

    <h3 class="sk-h3">c:buffer</h3>
    <div class="sk-content">
        <div class="sk-code"><pre class="brush: xml;">&lt;c:buffer var="jspPart"&gt;Hello World !&lt;/c:buffer&gt;</div>
        <p>var：java.lang.String - 必选 缓存的变量名称，支持el表达式。</p>
        <p>将该标签的内容执行之后放到pageContext中。</p>
    </div>

    <h4 class="sk-h4">c:bean</h4>
    <div class="sk-content">
        <div class="sk-code">
            <pre class="brush: xml;">
                &lt;c:bean name="myBean" className="test.MyBean"&gt;
                    &lt;c:constructor type="java.lang.Integer" value="1"/&gt;
                    &lt;c:constructor type="java.lang.Integer" value="1"/&gt;
                    &lt;c:constructor type="java.lang.Integer" value="1"/&gt;
                &lt;/c:bean&gt;
            </pre>
        </div>
        <p>name：java.lang.String - 必选 bean名称。</p>
        <p>className：java.lang.String - 必选 class名称。</p>
        <p>根据指定的className创建一个bean对象。</p>
    </div>

    <h4 class="sk-h4">c:map</h4>
    <div class="sk-content">
        <div class="sk-code">
            <pre class="brush: xml;">
                &lt;c:map name="myMap"&gt;
                    &lt;c:entry name="a" value="${myVar1}"/&gt;
                    &lt;c:entry name="b" value="${myVar2}"/&gt;
                    &lt;c:entry name="c" value="${myVar3}"/&gt;
                &lt;/c:map&gt;
            </pre>
        </div>
        <p>name：java.lang.String - 必选 map名称。</p>
        <p>构建一个map对象放到pageContext中。</p>
    </div>

    <h4 class="sk-h4">c:list</h4>
    <div class="sk-content">
        <div class="sk-code">
            <pre class="brush: xml;">
                &lt;c:list name="myList"&gt;
                    &lt;c:element value="${myVar1}"/&gt;
                    &lt;c:element value="${myVar2}"/&gt;
                    &lt;c:element value="${myVar3}"/&gt;
                &lt;/c:list&gt;
            </pre>
        </div>
        <p>name：java.lang.String - 必选 map名称。</p>
        <p>构建一个list对象放到pageContext中。</p>
    </div>
    <h4 class="sk-h4">c:xml</h4>
    <div class="sk-content">
        <div class="sk-code">
            <pre class="brush: xml;">
                &lt;c:xml file="${savePath}/1.txt" escapeXml="false" encoding="utf-8" out="true" trim="true"&gt;
                    &lt;h1&gt;Hello World !&lt;/h1&gt;
                &lt;/c:xml&gt;
            </pre>
        </div>
        <p>file：java.lang.String - 可选 输出内容到指定文件。</p>
        <p>escapeXml：java.lang.String - 可选 是否做xml编码。</p>
        <p>encoding：java.lang.String - 可选 文件编码。</p>
        <p>out：java.lang.String - 可选 是否同步输出到当前页面。</p>
        <p>trim：java.lang.String - 可选 是否trim之后输出。</p>
        <p>将内容输出到指定位置。</p>
    </div>

    <h4 class="sk-h4">c:output</h4>
    <div class="sk-content">
        <p>完全同c:xml，只是标签名称不一样。</p>
    </div>

    <h4 class="sk-h4">c:include</h4>
    <div class="sk-content">
        <div class="sk-code">
            <pre class="brush: xml;">
                &lt;c:include page="/user/userList.jsp"&gt;
                    &lt;c:param name="user1" value="${myUser}"/&gt;
                    &lt;c:param name="user2" value="${myUser}"/&gt;
                &lt;/c:include&gt;
            </pre>
        </div>
        <p>包含一个页面。</p>
        <p>此处的包含不同于t:include，t:include是编译指令，在编译期间先加载进来再一起编译，是静态包含，c:include是动态包含，被包含的页面将会单独编译，单独运行，然后把执行结果输出到当前页面。</p>
        <p>因此被包含的页面与当前页面在运行环境上是完全隔离的，互相之间变量不会相互污染。</p>
    </div>

    <h4 class="sk-h4">c:invoke</h4>
    <div class="sk-content">
        <div class="sk-code">
            <pre class="brush: xml;">
                &lt;c:invoke page="/user/userList.jsp"&gt;
                    &lt;c:param name="user1" value="${myUser}"/&gt;
                    &lt;c:param name="user2" value="${myUser}"/&gt;
                &lt;/c:invoke&gt;
            </pre>
        </div>
        <p>完全同c:include，只是标签名称不一样。可以用来调用另外一个模板。</p>
    </div>

    <h4 class="sk-h4">c:layout</h4>
    <div class="sk-content">
        <div class="sk-code">
            <pre class="brush: xml;">
                &lt;c:layout page="/user/userList.jsp"&gt;
                    &lt;c:param name="user1" value="${myUser}"/&gt;
                    &lt;c:param name="user2" value="${myUser}"/&gt;
                &lt;/c:layout&gt;
            </pre>
        </div>
        <p>完全同c:include，只是标签名称不一样。可以用来实现layout布局。</p>
    </div>

    <h4 class="sk-h4">c:action</h4>
    <div class="sk-content">
        <div class="sk-code">
            <pre class="brush: xml;">
                &lt;c:action className="test.UserAction" method="execute" page="/user/userList.jsp"&gt;
                    &lt;c:param name="user1" value="${myUser}"/&gt;
                    &lt;c:param name="user2" value="${myUser}"/&gt;
                &lt;/c:action&gt;
            </pre>
        </div>
        <p>className：java.lang.String - 可选 要执行的类。</p>
        <p>execute：java.lang.String - 可选 要执行的方法。</p>
        <p>page：java.lang.String - 可选 要渲染的页面。</p>
        <p>完全同c:include，只是标签名称不一样。</p>
        <p>c:include, c:invoke, c:layout, c:action实际上都是同一个标签，为了区分功能不同才定义出来不同的标签。</p>
        <p>c:action用来实现类似struts的s:action标签功能。它可以在调用完一个指定的类之后再渲染指定的页面。类相当于controller，页面相当于view。</p>
        <p>最重要的是被包含的页面与当前页面在运行环境上是完全隔离的，互相之间变量不会相互污染。</p>
        <p>由于变量之间互相隔离，<b>因此可以很方便的用它来自定义标签。</b></p>
        <p>c:action是一个很有用的标签，对于一些页面可以很方便的开发出来重用模块，并且他们之间是完全隔离的，互不影响。提高代码重用性很有用。例如：</p>

        <div class="sk-code">
            <pre class="brush: xml;">
                &lt;!--
                    这是一个首页的分类列表页面，可能很多页面都会有这样的逻辑。
                    c:action标签会调用ListAction执行查询动作，并把数据放到context中，然后调用testList.jsp进行渲染。
                    外层的缓存标签会先检查缓存，如果有了直接输出，否则执行全部子节点，并把结果缓存。
                    c:cache标签非Ayada提供，需要自己定义，因为每个应用使用的缓存系统可能都不一样。
                --&gt;
                &lt;c:cache key="cache:index:${categoryId}" expires="180"&gt;
                    &lt;c:action className="test.ListAction" method="execute" page="/test/testList.jsp"&gt;
                        &lt;c:param name="categoryId" value="${categoryId}"/&gt;
                    &lt;/c:action&gt;
                &lt;/c:cache&gt;
            </pre>
        </div>
    </div>

    <h2 class="sk-h2">4 用模版生成模版</h2>
    <div class="sk-content">
        <p>用模版生成模版有三种方法, 三种方法都是只针对当前页面生效, 不会影响其他页面.</p>
        <p class="bb"># 方法一: 重新定义全部标签</p>
        <p>1). 通过t:taglib指令覆盖全部指令, 将前缀从默认的c改成d。</p>
        <p>此方法需要将配置文件中的ayada.compile.standard-library=true改为ayada.compile.standard-library=false，系统将不自动加载默认的标签库。</p>
        <p>配置项的修改影响全局，这个时候需要所有的页面都使用t:taglib引入默认的标签库。</p>
        <div class="sk-code"><pre class="brush: xml;">&lt;t:taglib prefix="d" uri="http://localhost/ayada-taglib-standard"/&gt;</pre></div>
        <p>无需担心库文件中定义的标签名称包含的前缀，导入时会使用新的前缀替换。</p>
        <p>ayada-taglib-default.xml是系统自带的标签库定义文件，http://localhost/ayada-taglib-standard与http://localhost/ayada-taglib-default完全等效。</p>
        <p>2). 模板代码如下</p>
        <div class="sk-code">
            <pre class="brush: xml;">
            &lt;t:taglib prefix="d" uri="http://localhost/ayada-taglib-mytest"/&gt;
            &lt;c:comment>这是一个用模版生成模版的示例&lt;/c:comment&gt;
            &lt;d:if test="${1 == 1}"&gt;
                &lt;c:if test="${?a > 1}"&gt;
                    &lt;h1>${?user.name}&lt;/h1&gt;
                &lt;/c:if&gt;
            &lt;/d:if&gt;
            </pre>
        </div>
        <p>输出:</p>
        <div class="sk-code">
            <pre class="brush: xml;">
                <c:if test="${a > 1}">
                    <h1>${user.name}</h1>
                </c:if>
            </pre>
        </div>
        <p class="bb"># 方法二: 重新定义部分标签</p>
        <p>1). 通过t:rename指令重命名指定指令.</p>
        <p>2). 模板代码如下</p>
        <div class="sk-code">
            <pre class="brush: xml;">
                &lt;t:rename name="d:if" from="c:if"/&gt;
                &lt;c:comment&gt;这是一个用模版生成模版的示例&lt;/c:comment&gt;

                &lt;d:if test="${1 == 1}"&gt;
                    &lt;c:if test="${?a &gt; 1}"&gt;
                        &lt;h1&gt;${?user.name}&lt;/h1&gt;
                    &lt;/c:if&gt;
                &lt;/d:if&gt;
            </pre>
        </div>
        <p>输出:</p>
        <div class="sk-code">
            <pre class="brush: xml;">
                &lt;c:if test="${a &gt; 1}"&gt;
                    &lt;h1&gt;${user.name}&lt;/h1&gt;
                &lt;/c:if&gt;
            </pre>
        </div>
        <p class="bb"># 方法三: 使用t:text指令</p>
        <p>注：该方法只适合静态内容, 不适合动态内容.</p>
        <p>示例：</p>
        <div class="sk-code">
            <pre class="brush: xml;">
                &lt;t:text&gt;
                    &lt;c:if test="${a &gt; 1}"&gt;
                        &lt;h1&gt;${user.name}&lt;/h1&gt;
                    &lt;/c:if&gt;
                &lt;/t:text&gt;
            </pre>
        </div>
        <p>输出: </p>
        <div class="sk-code">
            <pre class="brush: xml;">
                &lt;c:if test="${a &gt; 1}"&gt;
                    &lt;h1&gt;${user.name}&lt;/h1&gt;
                &lt;/c:if&gt;
            </pre>
        </div>
        <p>t:text指令是在编译期执行的, 并且不允许嵌套. 编译期遇到t:text指令, 就会把其中的内容全部作为文本加载, 其中的el表达式也不需要使用特殊写法.</p>
        <p>因此中间任何内容都不会动态执行, 所以它只适合静态内容.</p>

        <p># 生成EL表达式.</p>
        <p>以上两种方法都是针对标签的重定义, 对于el表达式只需要在el表达式的前面加上?即可. 这样el表达式将会原样输出.</p>
        <p>例如: </p>
        <div class="sk-code"><pre class="brush: xml;">&lt;h1&gt;${?user.name}&lt;/h1&gt;</pre></div>
        <p>输出:</p>
        <div class="sk-code"><pre class="brush: xml;">&lt;h1&gt;${user.name}&lt;/h1&gt;</pre></div>
    </div>

    <a name="cp_5"></a>
    <h2 class="sk-h2">5. 自定义标签</h2>
    <div class="sk-content">
        <p>Ayada的API与jstl的API基本保持一致，除了包名不一样其他的类名都完全一致，类结构也一模一样。所以只需参考jstl的自定义标签即可。webapp目录下包含了绝大部分的代码示例和测试用例，这些文件基本上都以某个具体的标签名命名，从文件名就可以很容易看出来是关于那个标签的用法的。唯一需要做的是把引入的jstl的类的包名替换成Ayada的包名。</p>
        <p>在Jsp2.0中，自定义标签可以使用tag文件直接定义，不需要写java代码。Ayada不支持这种方式，Ayada建议使用另外的一种方式定义。直接使用Ayada的c:action引入一个模板即可，并且支持参数传递。这样实现的好处是不需要对这种标签专门编译，引擎实现简单。而且c:action功能更加强大。具体请参考c:action标签。</p>
        <p>还有一种方案是采用java代码编写标签，这种方式要求对jstl标签的API比较熟悉，对标签的生命周期很了解。好处是功能实现都封装在java类里面。</p>
        <p>下面是c:out标签的默认实现，示例代码：</p>
        <div class="sk-code">
            <pre class="brush: java;">
            package com.skin.ayada.jstl.core;

            import java.io.IOException;
            import java.io.Writer;

            import com.skin.ayada.runtime.JspWriter;
            import com.skin.ayada.runtime.PageContext;
            import com.skin.ayada.tagext.BodyContent;
            import com.skin.ayada.tagext.BodyTag;
            import com.skin.ayada.tagext.BodyTagSupport;
            import com.skin.ayada.tagext.Tag;

            /**
             * &lt;p&gt;Title: OutTag&lt;/p&gt;
             * &lt;p&gt;Description: &lt;/p&gt;
             * &lt;p&gt;Copyright: Copyright (c) 2006&lt;/p&gt;
             * @author xuesong.net
             * @version 1.0
             */
            public class OutTag extends BodyTagSupport {
                private Object value = null;
                private String defaultValue = null;
                private boolean escapeXml = false;

                @Override
                public int doStartTag() throws Exception {
                    if(this.value != null) {
                        OutTag.print(this.pageContext, this.value, this.escapeXml);
                        return Tag.SKIP_BODY;
                    }

                    if(this.defaultValue != null) {
                        OutTag.print(this.pageContext, this.defaultValue, this.escapeXml);
                        return Tag.SKIP_BODY;
                    }
                    return BodyTag.EVAL_BODY_BUFFERED;
                }

                /**
                 * @return int
                 */
                @Override
                public int doEndTag() throws Exception {
                    BodyContent bodyContent = this.getBodyContent();

                    if(bodyContent != null) {
                        print(this.pageContext, bodyContent.getString().trim(), this.escapeXml);
                    }
                    return EVAL_PAGE;
                }

                /**
                 * @param out
                 * @param value
                 * @param escapeXml
                 */
                public static void write(Writer out, Object value, boolean escapeXml) throws IOException {
                    if(value != null) {
                        String content = value.toString();

                        if(escapeXml) {
                            out.write(escape(content));
                        }
                        else {
                            out.write(content);
                        }
                    }
                }

                /**
                 * @param pageContext
                 * @param value
                 * @param escapeXml
                 */
                public static void print(PageContext pageContext, Object value, boolean escapeXml) throws IOException {
                    if(value != null) {
                        String content = value.toString();
                        JspWriter out = pageContext.getOut();

                        if(escapeXml) {
                            out.print(escape(content));
                        }
                        else {
                            out.print(content);
                        }
                    }
                }

                /**
                 * @param source
                 * @return String
                 */
                private static String escape(String source) {
                    if(source == null) {
                        return "";
                    }

                    char c;
                    StringBuilder buffer = new StringBuilder();

                    for(int i = 0, size = source.length(); i &lt; size; i++) {
                        c = source.charAt(i);

                        switch (c) {
                            case '&': {
                                buffer.append("&amp;");
                                break;
                            }
                            case '"': {
                                buffer.append("&quot;");
                                break;
                            }
                            case '&lt;': {
                                buffer.append("&lt;");
                                break;
                            }
                            case '&gt;': {
                                buffer.append("&gt;");
                                break;
                            }
                            case '\'': {
                                buffer.append("&#39;");
                                break;
                            }
                            default : {
                                buffer.append(c);
                                break;
                            }
                        }
                    }
                    return buffer.toString();
                }

                /**
                 * @return the value
                 */
                public Object getValue() {
                    return this.value;
                }

                /**
                 * @param value the value to set
                 */
                public void setValue(Object value) {
                    this.value = value;
                }

                /**
                 * @return boolean
                 */
                public boolean getEscapeXml() {
                    return this.escapeXml;
                }

                /**
                 * @param escapeXml
                 */
                public void setEscapeXml(boolean escapeXml) {
                    this.escapeXml = escapeXml;
                }
            }
            </pre>
        </div>
    </div>

    <h2 class="sk-h2">6 配置项</h2>
    <div class="sk-content">
        <p>Ayada的配置项只有5个：</p>
        <div class="sk-code">
            <pre class="brush: bash;">
                # 默认的编译模版。非必选项，在编译模式下，使用该模板生成java文件，如果没有配置使用默认值。
                ayada.compile.java-template=com/skin/ayada/compile/class.jsp

                # 模板文件扩展名，其他扩展名将被认为是静态文本。
                ayada.compile.source-pattern=jsp,jspf,jspx,tpl

                # 是否允许使用java脚本，该选项只在编译模式下有效。
                ayada.compile.ignore-jsptag=true

                # 是否默认加载系统自带的jstl库，如果为false，需要在模板文件中使用t:taglib手动引入。
                ayada.compile.standard-library=true

                # 是否使用fast-jstl模式，只在编译模式下有效，该选项生成的java代码更精炼效率更高。默认为true。
                ayada.compile.fast-jstl=true
            </pre>
        </div>
        <p>注：所有的配置项都不是必选项，只有在需要修改配置项的时候才需要存在配置文件，否则连配置文件都不需要。</p>
    </div>
    <h2 class="sk-h2">7 全局变量和工具类</h2>
    <div class="sk-content">
        <p class="bb">全局变量定义和工具类定义都在classpath下的ayada-tools.properties文件中定义，会合并Ayada自带的默认配置。该配置文件支持@import指令，并且可以直接书写中文不需要做unicode编码。</p>
        <p>默认配置ayada-tools-default.properties在ayada-1.0.0.x.jar包中定义。</p>
        <p>ayada-tools.properties文件支持两种语法：var定义一个字符串变量，set定义一个类实例。</p>
        <p>全局变量的配置通过DefaultExpressionFactory类来加载，如果自定义了ExpressionFactory，那么需要自己加载全局变量。</p>
        <p class="bb">常用工具类</p>
        <p>默认的配置定义了一些常用的工具类：</p>
        <div class="sk-code">
            <pre class="brush: bash;">
                @import META-INF/mytools.properties
                # bean工具类，常用方法：isEmpty， notEmpty，size，均支持任意类型，自动适配。
                set util=com.skin.ayada.jstl.util.BeanUtil

                # URL编码工具类，encode，decode等
                set URLUtil=com.skin.ayada.jstl.util.URLUtil

                # HTML编码工具类，encode，decode，remove等
                set HtmlUtil=com.skin.ayada.jstl.util.HtmlUtil

                # 时间日期工具类，parse，format，支持long型format，自动适配。smart方法输出友好的时间值。
                set DateUtil=com.skin.ayada.jstl.util.DateUtil

                # String工具类，length，substring，replace，split，escape，trim等。
                set StringUtil=com.skin.ayada.jstl.util.StringUtil

                # BBCode工具类，decode，getHtml，replace，remove等
                set bbcode=com.skin.ayada.jstl.util.BBCode

                # 国际化工具类，getBundle，getLocal，返回具体的国际化上下文对象
                set I18n=com.skin.ayada.jstl.util.I18n

                # 系统变量，默认的el表达式编码器，对所有的el表达式的输出结果进行html编码。
                set ELEncoder=com.skin.ayada.jstl.util.HtmlEncoder

                # 系统变量，默认的时区
                var ayada.time-zone=Asia/Chongqing

                # 系统变量，默认的地区
                var ayada.locale=zh_cn
            </pre>
        </div>
        <p class="bb">示例代码：</p>
        <div class="sk-code">
            <pre class="brush: xml;">
                &lt;c:set var="myHtml" value="<>"/&gt;
                &lt;c:set var="myHtml" value="&amp;lt;&amp;gt;"/&gt;
                上面的两个c:set标签等价，建议模板文件中对标签的属性进行html编码。
                el表达式会做html编码，例如下面的el输出&amp;lt;&amp;gt;
                ${myHtml}

                &lt;!-- 获取bundle并放到pageContext中，bundle是一个LocalizationContext对象 --&gt;
                &lt;c:set var="myBoundle" value="${I18n.getBundle(myBaseName, myValue)}"/&gt;

                <!-- 使用fmt:message标签输出，如果boundle是字符串，将会把boundle作为basename获取一个bundle。-->
                &lt;fmt:message key="" bundle="${myBoundle}"&gt;
                    &lt;fmt:param value="${param1}"/&gt;
                    &lt;fmt:param value="hello"/&gt;
                &lt;/fmt:message>

                <!-- 也可以使用el表达式直接输出 -->
                ${myBoundle.message("userEdit.saveBtn")}
                ${DateUtil.format(1410000000, "yyyy-MM-dd HH:mm:ss")}
            </pre>
        </div>
    </div>

    <h2 class="sk-h2">8 缺省的timeZone和loacl</h2>
    <div class="sk-content">
        <p>Ayada支持设置默认的timeZone和loacl，pageContext中都包含对应的get/set方法，这两个参数在国际化中使用。</p>
        <p>也可以通过fmt:setTimeZone和fmt:setLocale标签重置这些值。fmt:setTimeZone和fmt:setLocale标签只针对当前页面生效。</p>
        <p>对应的配置文件是：ayada-tools.properties，需要放在应用的classpath下。</p>
        <div class="sk-code">
            <pre class="brush: bash;">
                # 默认的时区
                var ayada.time-zone=Asia/Chongqing

                # 默认的地区
                var ayada.locale=zh_cn
            </pre>
        </div>
        <p>注：ayada-tools.properties中，凡是var定义的变量都是字符串，凡是set定义的变量系统会认为是一个类名，通过反射创建一个，全局唯一，所有pageContext共用同一个对象。</p>
    </div>

    <h2 class="sk-h2">9 jsp表达式和el表达式编码</h2>
    <div class="sk-content">
        <p>默认情况下所有的jsp表达式和el表达式都会做html编码。有时候页面上有些数据不需要编码，有三种方法可以避免编码。</p>
        <p class="bb">1. 在el表达式上加#前缀，所有以#开头的el表达式都不会做编码。该方式只针对el表达式生效，jsp表达式不允许使用此方法。</p>
        <p>例如：${#user.name}</p>
        <p class="bb">2. 使用c:out标签。</p>
        <p>所有的标签输出都由标签控制，引擎不做任何处理。</p>
        <p>例如：&lt;c:out value="${user.name}"/&gt;</p>
        <p class="bb">3. 使用c:escape标签临时修改编码，处理完再用c:escape改回去。</p>
        <p>例如：&lt;c:escape encoder="null"/&gt;${user.name}&lt;c:escape encoder="html"/&gt;${user.name}</p>
    </div>

    <h2 class="sk-h2">10 格式控制规则</h2>
    <div class="sk-content">
        <p>Ayada支持精细的控制输出格式，避免多余的空格和回车。</p>
        <p>一般情况下，模板引擎对于空格和回车的控制都比较困难，原因是因为模板引擎不知道什么时候该忽略空格和回车。
        由于很难确定该如何处理空格和回车，jsp引擎一般都遵循忠实的输出空格和回车的规则。如果希望控制输出格式，模板编写者需要自己组织代码格式，通过严格的代码格式来控制空格和回车。
        但是这样会导致模板本身的代码结构很不好，很可能为了避免空格而使代码堆砌在一起。
        jsp2.0在page指令中引入了一个新的属性trimDirectiveWhitespaces用来处理指令多余的空格，应该是不支持标签和jsp脚本的空格的。</p>
        <p>Ayada对标签定义做了扩展，可以通过对标签定义如何处理标签前后的空格和回车，并对jsp脚本和jsp指令均做了空格和回车的处理。</p>
        <p>下面是Ayada的处理规则：</p>
        <ul class="sk-list">
            <li><p>jsp指令、jsp声明、jsp脚本、Ayada指令，都会统一删除掉前导空格和后缀回车。不支持配置，统一处理。</p></li>
            <li><p>标签配置增加ignore-whitespace选项，可选值[true|false]，如果是true，删除掉前导空格和后面的回车。默认是true。t:import指令对应的属性是ignoreWhitespace。</p></li>
        </ul>
        <p>前导空格包括空格和\t符。</p>
        <p>后缀回车包括\r和\n。只删除第一个回车换行。</p>
        <p>&nbsp;</p>
        <p>编译程序在编译出来所有节点列表之后，会对节点列表进行两次扫描：</p>
        <p>1. 第一次扫描，根据标签的ignoreWhitespace定义决定是否对标签做格式处理。</p>
        <p>如果ignoreWhitespace为true，则删除标签的前导空格，前导空格是指标签前面的第一个文本节点的尾空格。</p>
        <p>删除标签之后的第一个回车换行符。标签的开始节点和结束节点都按照这个的规则处理。所有的jsp指令、脚本、声明也按照这个规则处理。</p>
        <p>2. 第二次扫描，根据标签的bodyContent定义清除标签内的文本节点或者。</p>
        <p class="bb">总的原则是：标签或者脚本所占的行不输出。即：把标签或者脚本所占的行删除之后就是最终输出的格式。</p>
    </div>

    <h2 class="sk-h2">11 Ayada的核心对象</h2>
    <div class="sk-content">
        <p class="bb">Ayada的核心对象是提供对外的API，外部程序通过核心对象来调用Ayada模板引擎。</p>
        <table class="sk-grid">
            <tr>
                <td style="width: 200px">类</td>
                <td style="width: 200px">类型</td>
                <td style="width: 200px">实现</td>
                <td style="width: 200px">说明</td>
            </tr>
            <tr>
                <td>TemplateContext</td>
                <td>接口</td>
                <td>
                    <p>DefaultTemplateContext</p>
                </td>
                <td>
                    <p>TemplateContext是一个接口，所有对外的API都通过TemplateContext来提供。</p>
                    <p>TemplateContext负责管理模板相关的上下文，提供创建模板，缓存模板，自动重新加载等功能。</p>
                </td>
            </tr>
            <tr>
                <td>SourceFactory</td>
                <td>接口</td>
                <td>
                    <p>DefaultSourceFactory</p>
                    <p>ClassPathSourceFactory</p>
                    <p>ZipSourceFactory</p>
                    <p>MemorySourceFactory</p>
                </td>
                <td>SourceFactory用来管理模板源文件，提供加载模板，模板修改检测等功能。</td>
            </tr>
            <tr>
                <td>TemplateFactory</td>
                <td>实现</td>
                <td>
                    <p>TemplateFactory</p>
                    <p>JspTemplateFactory</p>
                </td>
                <td><p>TemplateFactory编译模板文件并返回一个模板对象。JspTemplateFactory会将模板对象再次编译为class文件。</p></td>
            </tr>
            <tr>
                <td>ExpressionFactory</td>
                <td>接口</td>
                <td>
                    <p>DefaultExpressionFactory</p>
                </td>
                <td><p>ExpressionFactory用来创建el表达式上下文。</p></td>
            </tr>
        </table>
        <p class="bb">代码示例：</p>
        <div class="sk-code">
            <pre class="brush: java;">
            String home = servletContext.getRealPath("/");
            SourceFactory sourceFactory = new DefaultSourceFactory(home);
            TemplateFactory templateFactory = new TemplateFactory();
            TemplateContext templateContext = new DefaultTemplateContext();
            ExpressionFactory expressionFactory = new DefaultExpressionFactory();
            templateContext.setSourceFactory(sourceFactory);
            templateContext.setTemplateFactory(templateFactory);
            templateContext.setExpressionFactory(expressionFactory);

            StringWriter writer = new StringWriter();
            Map&lt;String, Object&gt; context = new HashMap&lt;String, Object&gt;();
            context.put("user", user);

            templateContext.execute("/user/userEdit.jsp", context, writer);
            System.out.println(writer.toString());
            </pre>
        </div>
    </div>

    <h2 class="sk-h2">12 编译模式和预编译</h2>
    <div class="sk-content">
        <p class="bb">编译模式需要使用JspTemplateFactory类来构建TemplateContext对象。TemplateContext对象使用TemplateFactory类的实现来加载模板。</p>
        <p>一般的线上应用在应用重启之后可以通过脚本访问一下指定的url来预热，这种情况不需要预编译。如果一定要预编译一下，可以通过调用com.skin.ayada.runtime.PrepareCompiler来进行预编译。</p>
        <p>PrepareCompiler会扫描指定目录下的所有文件作为模板文件进行编译，编译过程遵循配置项的sourde-pattern规则。也可以指定排除参数来排除不需要预编译的类。</p>
        <p>PrepareCompiler目前只支持文件系统，如果你使用了其他的方式加载模版文件，可以参考PrepareCompiler重新实现预编译。</p>
        <p class="bb">预编译的调用方式：</p>
        <div class="sk-code">
            <pre class="brush: java;">
                // 指定sourcePattern，此处指扩展名是jsp的文件作为模版编译，其他扩展名一律作为文本文件处理
                // 如果此处不指定，将使用默认配置
                templateContext.getSourceFactory().setSourcePattern("jsp");

                // 预编译必须指定一个已经定义好的模板上下文
                // 预编译的过程其实就是扫描全部文件，然后从模板上下文中加载模板对象并缓存起来
                // 如果以后文件又有修改，模板上下文将会重新加载该模板
                PrepareCompiler prepareCompiler = new PrepareCompiler(templateContext);

                // 排除指定目录和文件，遵循ant格式的路径匹配
                prepareCompiler.exclude(new String[]{"/resource/**", "/include/**", "/WEB-INF/**"});

                // 开始编译，如果某个模版文件编译失败，将会终止预编译
                prepareCompiler.compile();
            </pre>
        </div>
    </div>

    <h2 class="sk-h2">13 高级功能</h2>
    <div class="sk-content">
        <p>Ayada允许你直接操作编译出来的模板，你甚至可以通过编码来构建一个可执行的模板对象。Ayada的模板对象异常简单，它是一个list结构，而不是常规的树形结构。</p>
        <p>list结构的节点列表使得解释执行或者生成java代码都很方便。</p>
        <p>例如下面的文档：</p>
        <div class="sk-code">
            <pre class="brush: java;">
            &lt;h1&gt;这是一个节点列表示例&lt;/h1&gt;
            &lt;c:if test="${1 == 1}"&gt;
                &lt;c:if test="${1 == 1}"&gt;
                    ${userName} - Hello World !
                &lt;/c:if&gt;
            &lt;/c:if&gt;
            &lt;p&gt;结束了&lt;/p&gt;
            </pre>
        </div>
        <p>编译之后的存储结构：</p>
        <div class="sk-code">
            <pre class="brush: text;">
            0   [TEXT]: &lt;h1&gt;这是一个节点列表示例&lt;/h1&gt;\r\n
            1   [NODE]: &lt;c:if lineNumber="2" offset="1" length="7" tagClass="com.skin.ayada.jstl.core.IfTag" tagFactory="_tpl.com.skin.ayada.jstl.core.factory.IfTagFactory" test="${1 == 1}"&gt;
            2   [NODE]: &lt;c:if lineNumber="3" offset="2" length="5" tagClass="com.skin.ayada.jstl.core.IfTag" tagFactory="_tpl.com.skin.ayada.jstl.core.factory.IfTagFactory" test="${1 == 1}"&gt;
            3   [TEXT]:         
            4   [VARI]: ${userName}
            5   [TEXT]:  - Hello World !\r\n
            6   [NODE]: &lt;/c:if&gt;
            7   [NODE]: &lt;/c:if&gt;
            8   [TEXT]: &lt;p&gt;结束了&lt;/p&gt;\r\n
            </pre>
        </div>
        <p>下面是解释执行的伪代码：</p>
        <p>由于jstl规范定义的标签的生命周期的复杂性，详细的执行过程要比下面复杂一些，具体可参考com.skin.ayada.runtime.DefaultExecutor。</p>
        <p>编译为java文件的过程可参考com.skin.ayada.compile.JspCompiler。</p>
        <div class="sk-code">
            <pre class="brush: java;">
            List&lt;Node&gt; list = template.getNodes();
            int i = 0;
            int length = list.size();

            while(i < length) {
                out = pageContext.getOut();
                Node node = list.get(i);

                // 文本节点直接输出
                if(node.nodeType == NodeType.TEXT) {
                    i++;
                    out.write(node.getTextContent());
                    continue;
                }

                // 简单表达式，不需要调用el引擎
                if(node.nodeType == NodeType.VARIABLE) {
                    i++;
                    out.write(pageContext.getAttribute(node.getTextContent()));
                    continue;
                }

                // 表达式节点
                if(node.nodeType == NodeType.EXPRESSION) {
                    i++;
                    out.write(expressionContext.getValue(node.getTextContent()));
                    continue;
                }

                TagNode tag = (TagNode)node;

                if(tag.getOffset() == i) {
                    tag.setPageContext(pageContext);
                    tag.setParent(tag.getParent());
                    int flag = this.doStartTag(tag);

                    // 标签返回值是忽略标签体
                    if(flag == Tag.SKIP_BODY) {
                        i = i + tag.getLength() - 1;
                        continue;
                    }

                    // 标签返回值是终止渲染
                    if(flag == Tag.SKIP_PAGE) {
                        i = length;
                        continue;
                    }
                }
                else {
                    this.doEndTag(tag);
                }
                i++;
            }
            out.flush();
            </pre>
        </div>
        <p>编译之后会把节点对象存储为一个List对象，上面是打印出来的节点对象列表。其中每一个Node对象都有offset和length属性，offset属性表示当前节点在list中的下标。length属性表示当前节点对象的长度。</p>
        <p>当解释执行的时候，遍历list，如果是文本节点直接输出。如果是表达式节点则计算之后输出，如果是标签节点则按照标签的生命周期规范调用。</p>
        <p>经测试解释执行的性能比编译为class执行性能低30%。</p>
        <p>编译为java类的时候与上面的过程相同，只不过是输出为代码。</p>
        <p>list结构的节点列表使得解释执行非常方便，如果是树形结构，在处理标签节点的时候由于jstl标签生命周期的规范定义导致调用非常麻烦，流程不好控制，要么使用栈要么使用递归，实现复杂且性能不好。</p>
        <p>为了尽可能的少调用el引擎，在编译期会对el表达式做检查，如果是简单的el表达式，即符合java命名规则的表达式，会编译为Variable对象，而不是Expression对象。</p>
        <p>当解释执行的时候对于Variable节点可以直接调用out.write(pageContext.getAttribute(node.getTextContent()));</p>
        <p>当编译的时候可以直接生成类似的代码。</p>
    </div>

    <h2 class="sk-h2">14 不支持的语法</h2>
    <div class="sk-content">
        <p>Ayada对jsp某些语法不支持，例如：</p>
        <p>&lt;c:out value="&lt;%=request.getContextPath()%&gt;"/&gt;</p>
        <p>Ayada不支持在标签内使用jsp脚本，因为Ayada一开始只提供在解释模式下运行，编译模式是后来添加的。以后可能会提供对这种写法的支持，暂时不支持。</p>
    </div>

    <h2 class="sk-h2">15 线程模型</h2>
    <div class="sk-content">
        <p>首先标签不存在线程安全的问题，所有的标签都在安全的线程模型中执行。<b>除非自己实现的标签里面存在不安全的代码。</b></p>
        <p>在解释模式中不存在线程安全的问题。在编译模式下如果开启了java脚本选项，请小心使用类变量。编译模式下所有被编译好的模版对象都是单例的，不是线程安全的，这一点与jsp的运行模式是一致的。</p>
    </div>

    <h2 class="sk-h2">16 代码示例和测试用例</h2>
    <div class="sk-content">
        <p>在源代码的/webapp目录下包含了绝大部分的代码示例和测试用例，这些文件基本上都以某个具体的标签名命名，从文件名就可以很容易看出来是关于那个标签的用法的。</p>
        <p>com.skin.ayada.Main是java调用模板引擎的示例，也可以在命令行下调用。</p>
    </div>

    <h2 class="sk-h2">17 其他</h2>
    <div class="sk-content">
        <p>Ayada支持两种模式执行模版：解释模式和编译模式。解释模式直接在内存中通过解释执行来渲染结果。编译模式会先编译为java文件，再把java文件编译为class加载到jvm执行。</p>
        <p>通常模版引擎为了提升性能，会采用很多性能优化的手段，例如：动态编译，使用强制类型来避免反射等。Ayada也使用了动态编译，由于Ayada是与jsp和jstl完全一致的，所以一些jsp的技术在Ayada里面是完全有效的。</p>
        <p>另一方面，Ayada不可能采用某些优化技术。例如使用强制类型避免反射等。
        Ayada建议使用标签编写模版而不是使用java脚本。标签和el表达式都是不强制类型的。所以反射调用在ayada里面不可避免。
        如果追求极致性能，那么可以全部使用java将脚本编写模版，这样可以完全避免反射，但是那样的话使用模版引擎又有什么意义呢？还是建议使用标签编写。</p>
        <p class="bb">jstl语法的优点：</p>
        <ul>
            <li><p>jstl天然就是xml，与html无缝融合，不影响一些可视化编辑器；</p></li>
            <li><p>jstl编写出的模板代码简洁可读性好，对用户友好；</p></li>
            <li><p>强大的可扩展功能，标签可以封装一些常用组件，很好的提高了代码复用；</p></li>
            <li><p>jstl从jsp1.0至今已经10多年了，技术很成熟，功能很完善。</p></li>
        </ul>
        <p>当然使用标签编写会牺牲一些性能，但是权衡利弊，牺牲一些性能还是值得的。实际上很多系统真正影响性能的地方都不在展现层。</p>
        <p>Ayada尽可能在易用性，用户友好和性能之间寻找一个平衡点。模板语言的用户友好应该包括：</p>
        <ul>
            <li><p>易用，必须易上手，最好不需要看文档。</p></li>
            <li><p>代码简介，写出来的模版不能混乱不堪很难阅读。</p></li>
        </ul>
        <p>其实任何模板语言的核心语句只有三个：变量赋值，条件控制，循环语句，对应Ayada分别是：c:set，c:if，c:forEach，c:each是c:forEach的简写。
        这三个语句也符合任何一门编程语言的基本语句。只要知道jstl的这三个语句怎么用的对付基本的开发就够了。</p>
        <p class="bb">为什么不直接使用jsp，而是要再造一个轮子呢？</p>
        <ul>
            <li><p>统一实现，不同的jsp/servlet容器对jstl并不完全兼容。某些容器对标签属性和el的支持很不友好。这对于那些需要在不同的jsp/servlet容器中运行的应用程序来说很坑。</p></li>
            <li><p>某些功能的实现很麻烦，例如pageContext.include方法是在不同的pageContext中执行，父页面和被include的页面的pageContext是互相隔离的，传递参数只能通过其他方式。Ayada对pageContext.include做了扩展，可以直接传递参数。</p></li>
            <li><p>在非web环境中使用jstl。jstl本身专为web设计，API中直接跟web绑定死了，很难在其他环境中使用。Ayada中移除了web相关的对象，通过约定支持一些web中定义的对象。</p></li>
            <li><p>在jsp中页面和web环境绑定，同样的页面如果想生成静态文件必须修改jsp代码重定向输出到本地文件非常麻烦。使用模版引擎就可以很方便的执行已经编写好的jsp页面输出到指定文件。</p></li>
            <li><p>jsp中的jstl定义很繁琐，Ayada简化了标签定义。Ayada还提供了action，invoke标签来调用其他模版，将其他模版当成宏使用，实现上仅仅相当于一个方法调用。</p></li>
            <li><p>Ayada基本兼容jstl全部功能，原来使用jsp编写的代码基本上不需要修改就可以直接使用Ayada运行。</p></li>
            <li><p>Ayada自带的默认标签库扩展了大量常用标签，可以很方便的对代码隔离，避免大量的代码堆砌到一个页面，提高了代码可重用性。</p></li>
            <li><p>Ayada可以禁用java脚本，只使用标签编写，强制展现层和逻辑层分离，一定程度上也使web程序避免被人上传jsp木马。</p></li>
            <li><p>Ayada的el引擎可以任意替换，如果你喜欢其他的表达式语言，你可以把默认的el引擎替换为其他实现。</p></li>
            <li><p>在jsp中所有的jsp表达式和el输出都需要人工编码加上html转码，否则可能被xss注入。Ayada在引擎层默认对所有的jsp表达式和el表达式进行html编码，同时支持自定义转码函数。</p></li>
        </ul>
    </div>
    <h2 class="sk-h2">18 Ayada的实现原理</h2>
    <div class="sk-content">
        <p>首先Ayada的实现原理非常简单，没有使用到任何高大上的技术，都是最基本的java基础知识。</p>
        <p>虽然源代码大概有200多个类，但实际上核心实现类只有三个：TemplateCompiler、DefaultExecutor、JspCompiler，其他大部分的类都是用来组织引擎如何工作的。</p>
        <p>JspCompiler只有在编译为class的时候才会使用到，实际上关键实现只有两个。</p>
        <p>TemplateCompiler把一个模板文件编译为一个list结构的node列表，编译过程其实就是字符串解析，稍微看一下就能看懂，核心过程就是一个while循环，里面三个if分支。</p>
        <p>while循环依次读入一个个的字符，如果是&lt;就认为是节点开始，如果是${就认为是表达式开始，否则就认为是文本，根据三种情况生成三种节点，按照解析顺序放入list中，最后返回该list。</p>
        <p>DefaultExecutor是负责解释执行的，上面章节已经给出了伪代码。</p>
        <p>JspCompiler是负责把节点列表编译为一个java类，代码大概有1700行左右，绝大部分代码都是在做性能优化，尽可能生成高效的java代码，如果除去优化的代码，可能一半的代码量都不到。</p>
    </div>
</div>
<div class="h20" style="height: 200px;"></div>
<div class="scrolltop" onclick="window.scrollTo(0, 0)"></div>
<script type="text/javascript">
<!--
function path() {
    var args = arguments;
    var result = [];

    for(var i = 0; i < args.length; i++) {
        result.push(args[i].replace("@", "resource/sh/"));
    }
    return result;
};

var args = path(
    "applescript            @shBrushAppleScript.js",
    "actionscript3 as3      @shBrushAS3.js",
    "bash shell             @shBrushBash.js",
    "coldfusion cf          @shBrushColdFusion.js",
    "cpp c                  @shBrushCpp.js",
    "c# c-sharp csharp      @shBrushCSharp.js",
    "css                    @shBrushCss.js",
    "delphi pascal          @shBrushDelphi.js",
    "diff patch pas         @shBrushDiff.js",
    "erl erlang             @shBrushErlang.js",
    "groovy                 @shBrushGroovy.js",
    "java                   @shBrushJava.js",
    "jfx javafx             @shBrushJavaFX.js",
    "js jscript javascript  @shBrushJScript.js",
    "perl pl                @shBrushPerl.js",
    "php                    @shBrushPhp.js",
    "text plain             @shBrushPlain.js",
    "py python              @shBrushPython.js",
    "ruby rails ror rb      @shBrushRuby.js",
    "sass scss              @shBrushSass.js",
    "scala                  @shBrushScala.js",
    "sql                    @shBrushSql.js",
    "vb vbnet               @shBrushVb.js",
    "xml xhtml xslt html    @shBrushXml.js"
);
SyntaxHighlighter.autoloader.apply(null, args);
SyntaxHighlighter.all();
//-->
</script>
</body>
</html>
